package cn.lingyangwl.agile.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.lingyangwl.agile.auth.*;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.module.tenant.*;
import cn.lingyangwl.agile.system.assembly.*;
import cn.lingyangwl.agile.system.manager.*;
import cn.lingyangwl.agile.system.model.entity.*;
import cn.lingyangwl.agile.system.model.rqrs.ram.*;
import cn.lingyangwl.agile.system.service.*;
import cn.lingyangwl.agile.tenant.core.utils.*;
import cn.lingyangwl.framework.tool.core.*;
import cn.lingyangwl.framework.tool.core.exception.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author shenguangyang
 */
@Service
public class AuthorizeServiceImpl implements AuthorizeService {

    @Resource
    private SysRoleMenuRelManager roleMenuRelManager;
    @Resource
    private SysMenuManager menuManager;
    @Resource
    private SysUserRoleManager userRoleManager;
    @Resource
    private SysUserDeptRelManager userDeptManager;
    @Resource
    private AuthApi authApi;
    @Resource
    private SysRoleManager roleManager;
    @Resource
    private SysDeptManager deptManager;
    @Resource
    private AuthorizeAssembly authorizeAssembly;
    @Resource
    private SysTenantManager tenantManager;
    @Resource
    private RamResourceService ramResourceService;
    @Resource
    private RamModuleService ramModuleService;
    @Resource
    private SysMenuService menuService;

    @Override
    public Map<String, List<String>> listAssignRoleUrl(List<Long> roleIds) {
        Map<String, String> moduleMap = ramModuleService.list().stream()
                .collect(Collectors.toMap(RamModule::getCode, RamModule::getServiceCode, (k1, k2) -> k1));

        Set<String> actions = TenantUtils.executeIgnore(DataIsolateLevel.ALL,
                () -> this.roleMenuRelManager.listActionsByRoleIds(roleIds)
        );
        return this.ramResourceService.list().stream().filter(e -> actions.contains(e.getAction()))
                .collect(
                        Collectors.groupingBy(e -> moduleMap.get(e.getModuleCode()),  Collectors.mapping(RamApiResource::getUrl, Collectors.toList()))
                );
    }


    @Override
    public List<ResourcePermResp> listApiResource(Long roleId) {
        Map<String, List<String>> actionMap = ramResourceService.listResource(new RamResourceListReq()).stream()
                .collect(Collectors.groupingBy(RamResourceResp::getAction, Collectors.mapping(RamResourceResp::getName, Collectors.toList())));

        LambdaQueryWrapper<SysRoleMenu> lqw = SysRoleMenu.lqw().eq(SysRoleMenu::getRoleId, roleId)
                .isNotNull(SysRoleMenu::getResourceAction);

        return roleMenuRelManager.list(lqw).stream()
                .filter(e -> StringUtils.isNotEmpty(e.getResourceAction()))
                .flatMap(e -> {
                    List<String> list = Optional.ofNullable(actionMap.get(e.getResourceAction())).orElse(Collections.emptyList());
                    return list.stream().map(resourceName -> {
                        ResourcePermResp resp = authorizeAssembly.toResourcePermResp(e);
                        resp.setName(resourceName);
                        return resp;
                    });
                }).collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveApiResource(ResourcePermSaveReq req) {
        // key: action, value: 菜单集合
        Map<String, List<Long>> menuMap = menuService.listMenu().stream()
                .filter(e -> StringUtils.isNotEmpty(e.getPerms()))
                .collect(Collectors.groupingBy(SysMenu::getPerms, Collectors.mapping(SysMenu::getId, Collectors.toList())));

        LambdaQueryWrapper<SysRoleMenu> lqw = SysRoleMenu.lqw().eq(SysRoleMenu::getRoleId, req.getSubjectId())
                .isNotNull(SysRoleMenu::getResourceAction);
        Set<String> oldActions = roleMenuRelManager.list(lqw).stream()
                .map(SysRoleMenu::getResourceAction)
                .filter(StringUtils::isNotEmpty)
                .collect(Collectors.toSet());

        String tenantId = TenantContext.get().getTenantId();
        Long sceneId = !TenantCons.SYSTEM_TENANT_ID.equals(tenantId)
                ?  tenantManager.getById(tenantId).getSceneId() : TenantCons.SYSTEM_TENANT_PACKAGE_ID;

        // 计算添加的数据
        Set<String> addActions = new HashSet<>(req.getActionList());
        addActions.removeAll(oldActions);

        // 计算被删除的元素
        Set<String> deleteActions = new HashSet<>(oldActions);
        req.getActionList().forEach(deleteActions::remove);

        List<SysRoleMenu> addRoleMenuList = addActions.stream().flatMap(action -> {
            List<Long> menuIds = menuMap.get(action);
            // 实现自动关联菜单
            if (CollectionUtils.isNotEmpty(menuIds)) {
                return menuIds.stream().map(menuId -> {
                    SysRoleMenu roleMenu = new SysRoleMenu();
                    roleMenu.setMenuId(menuId);
                    roleMenu.setRoleId(req.getSubjectId());
                    roleMenu.setResourceAction(action);
                    roleMenu.setSceneId(sceneId);
                    return roleMenu;
                });
            }
            SysRoleMenu roleMenu = new SysRoleMenu();
            roleMenu.setMenuId(0L); // 由于新添加的没有在菜单之中, 所以这里直接设置为0, 表示接口资源
            roleMenu.setRoleId(req.getSubjectId());
            roleMenu.setResourceAction(action);
            roleMenu.setSceneId(sceneId);
            return Stream.of(roleMenu);
        }).collect(Collectors.toList());

        if (CollectionUtils.isNotEmpty(deleteActions)) {
            this.roleMenuRelManager.deleteByRoleIdAndActions(req.getSubjectId(),deleteActions);
        }

        this.roleMenuRelManager.saveBatch(addRoleMenuList);

        // 刷新绑定该角色的客户端
        authApi.refreshClientAuthorityByRoleIds(Collections.singletonList(req.getSubjectId()), false);
    }

    @Override
    public void assignRoleMenu(Long roleId, Set<Long> mendIds) {
        if (Objects.isNull(roleId) || CollectionUtils.isEmpty(mendIds)) {
            return;
        }

        Map<Long, SysMenu> menuMap = menuManager.listMenuDataByIds(new ArrayList<>(mendIds)).stream()
                .collect(Collectors.toMap(SysMenu::getId, Function.identity()));

        mendIds = menuMap.values().stream().map(SysMenu::getId).collect(Collectors.toSet());

        Set<Long> dbMenuIds = roleMenuRelManager.listByRoleId(roleId).stream()
                .map(SysRoleMenu::getMenuId).collect(Collectors.toSet());

        // 计算新增和删除的菜单
        Collection<Long> createMenuIds = CollUtil.subtract(mendIds, dbMenuIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, mendIds);

        // 新增和删除
        if (!CollectionUtil.isEmpty(createMenuIds)) {
            List<SysRoleMenu> list = createMenuIds.stream()
                    .map(menuId -> new SysRoleMenu().setMenuId(menuId).setRoleId(roleId)
                            .setSceneId(menuMap.get(menuId).getSceneId()))
                    .collect(Collectors.toList());
            roleMenuRelManager.saveBatch(list);
        }
        if (!CollectionUtil.isEmpty(deleteMenuIds)) {
            roleMenuRelManager.deleteByRoleIdAndMenuIds(roleId, deleteMenuIds);
        }

        // 如果角色权限发生变化, 将用户强退
        if (CollectionUtils.isNotEmpty(createMenuIds) || CollectionUtils.isNotEmpty(deleteMenuIds)) {
            List<Long> userIds = userRoleManager.listUserIdsByRoleId(roleId);
            authApi.removeAccessTokenByUserIds(userIds);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assignUserRole(Long userId, List<Long> roleIds) {
        if (Objects.isNull(userId) || CollectionUtils.isEmpty(roleIds)) {
            return;
        }
        Map<Long, SysRole> roleRelMap = roleManager.listByIds(roleIds).stream()
                .collect(Collectors.toMap(SysRole::getId, Function.identity()));

        // 直接删除原有的所有用户角色
        userRoleManager.remove(SysUserRole.lqw().eq(SysUserRole::getUserId, userId));

        // 新增用户角色
        List<SysUserRole> userRoleList = roleIds.stream().filter(roleRelMap::containsKey)
                .map(roleId -> new SysUserRole().setUserId(userId).setRoleId(roleId).setRoleCode(roleRelMap.get(roleId).getRoleCode()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(userRoleList)) {
            throw new BizException("为用户分配角色失败, 请核实角色ID是否存在");
        }
        userRoleManager.saveBatch(userRoleList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assignUserDept(Long userId, List<Long> deptIds) {
        if (Objects.isNull(userId) || CollectionUtils.isEmpty(deptIds)) {
            return;
        }

        // 删除用户与部门的关联
        userDeptManager.deleteByUserId(userId);

        List<SysUserDeptRel> userDeptList = deptIds.stream()
                .map(deptId -> new SysUserDeptRel().setUserId(userId).setDeptId(deptId)).collect(Collectors.toList());
        // 新增用户与部门的关系
        userDeptManager.saveBatch(userDeptList);
    }

    @Override
    public void deleteTenantAllRole() {
        roleManager.deleteAllRole();
    }

    @Override
    public void deleteTenantAllDept() {
        deptManager.deleteAllDept();
    }

    @Override
    public Set<Long> listRoleMenuId(Long roleId) {
        return roleMenuRelManager.listByRoleId(roleId).stream().map(SysRoleMenu::getMenuId).collect(Collectors.toSet());
    }

    @Override
    public void deleteUserDept(List<Long> userIds) {
        if (CollectionUtils.isEmpty(userIds)) {
            return;
        }
        userDeptManager.remove(SysUserDeptRel.lqw().in(SysUserDeptRel::getUserId, userIds));

    }

    @Override
    public void deleteUserRole(List<Long> userIds) {
        if (CollectionUtils.isEmpty(userIds)) {
            return;
        }

        userRoleManager.remove(SysUserRole.lqw().in(SysUserRole::getUserId, userIds));
    }

    @Override
    public Set<String> listActionByUserId(Long userId) {
        return roleMenuRelManager.getBaseMapper().listActionByUserId(userId)
                .stream().filter(StringUtils::isNotEmpty).collect(Collectors.toSet());
    }
}
